05 - 第一次完整预训练与复盘
前面四篇产出的是代码,这一篇产出的是一次真实的训练记录。
需要说明这一篇的性质:它现在是模板,不是记录。 表格里的数字要等真开卡跑完才能填。之所以先写出来,是因为该记什么必须在开跑之前就定好——训练跑起来之后再想「早知道该记一下这个」,就得重跑。
前置:01 到 04 篇的验收清单全部通过。
一、开跑前的最后检查
前面每篇都有验收清单,这里只列跳过了就会烧掉真金白银的那几条。
| 检查项 | 出自 | 跳过的后果 |
|---|---|---|
train.bin 随机位置 decode 回文本通顺 | 01 篇六节 | 数据是乱的,17 小时全废 |
val.bin 的文档不在 train.bin 里 | 01 篇 5.7 | 验证 loss 偏低,误判模型变好 |
| 参数量对得上 502,193,664 | 02 篇 8.1 | 模型结构写错 |
| 初始 loss 在 10.37 附近 | 02 篇 8.3 | 偏低说明因果掩码失效,模型在抄答案 |
| 因果性测试通过 | 02 篇 8.4 | 同上,且这是唯一能主动发现它的手段 |
| 杀掉进程能从 checkpoint 恢复且曲线接得上 | 03 篇十节 | 实例被回收时无法续跑 |
| checkpoint 路径指向持久化云盘 | 03 篇 8.3 | 实例回收后 checkpoint 一起没 |
| 多卡 loss 曲线与单卡重合 | 04 篇六节 | 种子没错开,四张卡在算同样的数据 |
最后两条最容易漏,也最贵。 前者的代价是整轮训练白跑,后者的代价是花了四倍的钱得到单卡的效果。
二、跑起来之后盯什么
2.1 六个指标
| 指标 | 记录 频率 | 健康的样子 |
|---|---|---|
| 训练 loss | 每 10 步 | 从 10.37 持续下降 |
| 验证 loss | 每 200 步 | 跟随训练 loss,不明显背离 |
| grad norm | 每 10 步 | 稳定小幅波动,不长期贴着裁剪阈值 |
| 学习率 | 每 10 步 | 与 03 篇 5.3 打印的曲线一致 |
| tokens/s | 每 10 步 | 稳定,用来估剩余时间 |
| MFU | 每 10 步 | 30% 以上;低于 30% 查瓶颈 |
另外每次存 checkpoint 时记一下峰值显存和已消耗卡时。前者用来判断还能不能加大 batch,后者用来对成本。
2.2 日志要落盘,不要只打屏
torchrun 的输出在实例被回收时一起消失。把日志重定向到持久化云盘上,和 checkpoint 放一起:
torchrun --standalone --nproc_per_node=4 train_ddp.py --mode ddp \
2>&1 | tee /mnt/persistent/logs/run_$(date +%Y%m%d_%H%M).log
复盘时要回看的是曲线,不是最后一屏。
三、loss 曲线的四种形状
绝大多数训练问题在 loss 曲线上有特征,而且前两百步就能看出来。开跑之后先盯着前两百步,不对就立刻停,别等 17 小时。
对应的诊断:
| 形状 | 最可能的原因 | 先查什么 |
|---|---|---|
| ① 健康 | 正常 | 继续,但仍要看 grad norm |
| ② 中途发散 | loss 忘了除 grad_accum;学习率太高;没做梯度裁剪 | 03 篇 3.3、5.4、第六节 |
| ③ 完全不降 | 学习率被设成 0;忘了 optimizer.step();warmup 步数写成了总步数 | 03 篇五节 |
| ④ 降得异常快 | 因果掩码失效,模型在抄答案 | 02 篇 8.4 因果性测试 |
第四种最危险,因为它伪装成好消息。02 篇 8.3 说过:初始 loss 明显低于 10.37 是同一个问题的早期信号。
四、翻车现场与处置
按「你会先看到什么」组织,而不是按原因分类。
| 你看到的现象 | 大概率原因 | 处置 |
|---|---|---|
| 启动就 OOM,只有卡 0 显存满 | 忘了 torch.cuda.set_device(local_rank) | 04 篇 2.2 |
| 训练卡死,四张卡利用率 100% 但不动 | 某些 rank 少调了一次 collective | 设 TORCH_NCCL_BLOCKING_WAIT=1 让它报错,见 04 篇 5.3 |
| MFU 只有百分之十几 | 数据加载跟不上 | 检查 pin_memory;调大 micro_batch |
| 屏幕刷四份重复日志 | 没判断 rank == 0 | 04 篇 2.2 |
| 恢复 checkpoint 后 loss 跳一下 | 只存了权重,没存优化器状态 | 03 篇 8.1 |
| 恢复后学习率不对 | step 没存进 checkpoint | 同上 |
| 存 checkpoint 时进程被杀,旧的也没了 | 没做原子写 | 03 篇 8.2 |
中途发现要改超参怎么办。 如果只是学习率偏高,可以从最近的 checkpoint 恢复、调低学习率继续,不必从头。但如果是数据或模型结构的问题,从头重跑是唯一选择——这也是为什么第一节那张检查表要在开跑前全过一遍。